/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

#pragma once

/** \file
 * \ingroup freestyle
 * \brief Various tools for geometry
 */

#include <vector>

#include "Geom.h"

#include "../system/FreestyleConfig.h"

using namespace std;

namespace Freestyle {

using namespace Geometry;

namespace GeomUtils {

//
// Templated procedures
//
/////////////////////////////////////////////////////////////////////////////

/** Computes the distance from a point P to a segment AB */
template<class T> real distPointSegment(const T &P, const T &A, const T &B)
{
  T AB, AP, BP;
  AB = B - A;
  AP = P - A;
  BP = P - B;

  real c1(AB * AP);
  if (c1 <= 0) {
    return AP.norm();
  }

  real c2(AB * AB);
  if (c2 <= c1) {
    return BP.norm();
  }

  real b = c1 / c2;
  T Pb, PPb;
  Pb = A + b * AB;
  PPb = P - Pb;

  return PPb.norm();
}

//
// Non-templated procedures
//
/////////////////////////////////////////////////////////////////////////////
typedef enum {
  DONT_INTERSECT,
  DO_INTERSECT,
  COLINEAR,
  COINCIDENT,
} intersection_test;

intersection_test intersect2dSeg2dSeg(const Vec2r &p1,
                                      const Vec2r &p2,  // first segment
                                      const Vec2r &p3,
                                      const Vec2r &p4,  // second segment
                                      Vec2r &res);      // found intersection point

intersection_test intersect2dLine2dLine(const Vec2r &p1,
                                        const Vec2r &p2,  // first segment
                                        const Vec2r &p3,
                                        const Vec2r &p4,  // second segment
                                        Vec2r &res);      // found intersection point

intersection_test intersect2dSeg2dSegParametric(const Vec2r &p1,
                                                const Vec2r &p2,  // first segment
                                                const Vec2r &p3,
                                                const Vec2r &p4,  // second segment
                                                real &t,          // I = P1 + t * P1P2)
                                                real &u,          // I = P3 + u * P3P4
                                                real epsilon = M_EPSILON);

/** check whether a 2D segment intersect a 2D region or not */
bool intersect2dSeg2dArea(const Vec2r &min, const Vec2r &max, const Vec2r &A, const Vec2r &B);

/** check whether a 2D segment is included in a 2D region or not */
bool include2dSeg2dArea(const Vec2r &min, const Vec2r &max, const Vec2r &A, const Vec2r &B);

/** Box-triangle overlap test, adapted from Tomas Akenine-Möller code */
bool overlapTriangleBox(Vec3r &boxcenter, Vec3r &boxhalfsize, Vec3r triverts[3]);

/** Fast, Minimum Storage Ray-Triangle Intersection, adapted from Tomas Möller and Ben Trumbore
 * code. */
bool intersectRayTriangle(const Vec3r &orig,
                          const Vec3r &dir,
                          const Vec3r &v0,
                          const Vec3r &v1,
                          const Vec3r &v2,
                          real &t,  // I = orig + t * dir
                          real &u,
                          real &v,  // I = (1 - u - v) * v0 + u * v1 + v * v2
                          const real epsilon = M_EPSILON);  // the epsilon to use

/** Intersection between plane and ray adapted from Graphics Gems, Didier Badouel */
intersection_test intersectRayPlane(const Vec3r &orig,
                                    const Vec3r &dir,  // ray origin and direction
                                    // plane's normal and offset (plane = { P / P.N + d = 0 })
                                    const Vec3r &norm,
                                    const real d,
                                    real &t,                          // I = orig + t * dir
                                    const real epsilon = M_EPSILON);  // the epsilon to use

/** Intersection Ray-Bounding box (axis aligned).
 *  Adapted from Williams et al, "An Efficient Robust Ray-Box Intersection Algorithm", JGT 10:1
 * (2005), pp. 49-54.
 */
bool intersectRayBBox(const Vec3r &orig,
                      const Vec3r &dir,  // ray origin and direction
                      const Vec3r &boxMin,
                      const Vec3r &boxMax,  // the bbox
                      // the interval in which at least on of the intersections must happen
                      real t0,
                      real t1,
                      real &tmin,  // Imin = orig + tmin * dir is the first intersection
                      real &tmax,  // Imax = orig + tmax * dir is the second intersection
                      real epsilon = M_EPSILON);  // the epsilon to use

/** Checks whether 3D point P lies inside or outside of the triangle ABC */
bool includePointTriangle(const Vec3r &P, const Vec3r &A, const Vec3r &B, const Vec3r &C);

void transformVertex(const Vec3r &vert, const Matrix44r &matrix, Vec3r &res);

void transformVertices(const vector<Vec3r> &vertices, const Matrix44r &trans, vector<Vec3r> &res);

Vec3r rotateVector(const Matrix44r &mat, const Vec3r &v);

//
// Coordinates systems changing procedures
//
/////////////////////////////////////////////////////////////////////////////

/** From world to image
 *  p
 *    point's coordinates expressed in world coordinates system
 *  q
 *    vector in which the result will be stored
 *  model_view_matrix
 *    The model view matrix expressed in line major order (OpenGL
 *    matrices are column major ordered)
 *  projection_matrix
 *    The projection matrix expressed in line major order (OpenGL
 *    matrices are column major ordered)
 *  viewport
 *    The viewport: x,y coordinates followed by width and height (OpenGL like viewport)
 */
void fromWorldToImage(const Vec3r &p,
                      Vec3r &q,
                      const real model_view_matrix[4][4],
                      const real projection_matrix[4][4],
                      const int viewport[4]);

/** From world to image
 *  p
 *    point's coordinates expressed in world coordinates system
 *  q
 *    vector in which the result will be stored
 *  transform
 *    The transformation matrix (gathering model view and projection),
 *    expressed in line major order (OpenGL matrices are column major ordered)
 *  viewport
 *    The viewport: x,y coordinates followed by width and height (OpenGL like viewport)
 */
void fromWorldToImage(const Vec3r &p, Vec3r &q, const real transform[4][4], const int viewport[4]);

/** Projects from world coordinates to camera coordinates
 *  Returns the point's coordinates expressed in the camera's
 *  coordinates system.
 *  p
 *    point's coordinates expressed in world coordinates system
 *  q
 *    vector in which the result will be stored
 *  model_view_matrix
 *    The model view matrix expressed in line major order (OpenGL
 *    matrices are column major ordered)
 */
void fromWorldToCamera(const Vec3r &p, Vec3r &q, const real model_view_matrix[4][4]);

/** Projects from World Coordinates to retina coordinates
 *  Returns the point's coordinates expressed in Retina system.
 *  p
 *    point's coordinates expressed in camera system
 *  q
 *    vector in which the result will be stored
 *  projection_matrix
 *    The projection matrix expressed in line major order (OpenGL
 *    matrices are column major ordered)
 */
void fromCameraToRetina(const Vec3r &p, Vec3r &q, const real projection_matrix[4][4]);

/** From retina to image.
 *  Returns the coordinates expressed in Image coordinates system.
 *  p
 *    point's coordinates expressed in retina system
 *  q
 *    vector in which the result will be stored
 *  viewport
 *    The viewport: x,y coordinates followed by width and height (OpenGL like viewport).
 */
void fromRetinaToImage(const Vec3r &p, Vec3r &q, const int viewport[4]);

/** From image to retina
 *  p
 *    point's coordinates expressed in image system
 *  q
 *    vector in which the result will be stored
 *  viewport
 *    The viewport: x,y coordinates followed by width and height (OpenGL like viewport).
 */
void fromImageToRetina(const Vec3r &p, Vec3r &q, const int viewport[4]);

/** computes the coordinates of q in the camera coordinates system,
 *  using the known z coordinates of the 3D point.
 *  That means that this method does not inverse any matrices,
 *  it only computes X and Y from x,y and Z)
 *  p
 *    point's coordinates expressed in retina system
 *  q
 *    vector in which the result will be stored
 *  projection_matrix
 *    The projection matrix expressed in line major order (OpenGL
 *    matrices are column major ordered)
 */
void fromRetinaToCamera(const Vec3r &p, Vec3r &q, real focal, const real projection_matrix[4][4]);

/** Projects from camera coordinates to world coordinates
 *  Returns the point's coordinates expressed in the world's
 *  coordinates system.
 *  p
 *    point's coordinates expressed in the camera coordinates system
 *  q
 *    vector in which the result will be stored
 *  model_view_matrix
 *    The model view matrix expressed in line major order (OpenGL
 *    matrices are column major ordered)
 */
void fromCameraToWorld(const Vec3r &p, Vec3r &q, const real model_view_matrix[4][4]);

}  // end of namespace GeomUtils

} /* namespace Freestyle */
